﻿Imports System.Configuration.Provider

Public Class WebConfigRoleProvider
  Inherits Web.Security.RoleProvider

  Private _rolesRepository As Interfaces.IRolesRepository

  Public Sub New()
    ' Very basic dependency injection:
    ' Replace the line below to use a different repository. 
    ' Using an IoC container for this would be overkill as most users probably just
    ' want the behaviour that reads web.config with minimal fuss.
    _rolesRepository = New WebConfigRolesRepository
    '_rolesRepository = New StubRolesRepository ' Use for testing only!
  End Sub

  Public Overrides Sub AddUsersToRoles(ByVal usernames() As String, ByVal roleNames() As String) '
    Call CheckUserNames(usernames)
    Call CheckRoleNames(roleNames)

    For Each roleName In roleNames
      Dim role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

      If Not role Is Nothing Then
        Call role.Users.AddRange(usernames)
      Else
        Throw New ProviderException("Role '{0}' does not exist.".FormatThis(roleName))
      End If
    Next

    Call Save()
  End Sub

  Private _strApplicationName As String
  Public Overrides Property ApplicationName() As String
    Get
      Return _strApplicationName
    End Get
    Set(ByVal value As String)
      _strApplicationName = value
    End Set
  End Property

  Public Overrides Sub CreateRole(ByVal roleName As String)
    Call CheckRoleName(roleName)

    If Not RoleExists(roleName) Then
      Call _rolesRepository.Roles.Add(New Role(roleName, Nothing))
      Call Save()
    Else
      Throw New ProviderException("Role '{0}' already exists.".FormatThis(roleName))
    End If
  End Sub

  Public Overrides Function DeleteRole(ByVal roleName As String, ByVal throwOnPopulatedRole As Boolean) As Boolean
    Call CheckRoleName(roleName)

    Dim role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

    If Not role Is Nothing Then
      Dim intUserCount As Integer = role.Users.Count

      If (intUserCount <> 0) And throwOnPopulatedRole Then
        Throw New ProviderException("Role '{0}' cannot be deleted because {1} user{2} has this role.".FormatThis(roleName, intUserCount.ToString, If(intUserCount = 1, "", "s")))
      End If

      Call _rolesRepository.Roles.Remove(role)
      Call Save()
    Else
      Throw New ArgumentException("Role '{0}' does not exist.".FormatThis(roleName))
    End If
  End Function

  Public Overrides Function FindUsersInRole(ByVal roleName As String, ByVal usernameToMatch As String) As String()
    Dim role As Role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

    If Not role Is Nothing Then
      Return (From u In role.Users Where u.StartsWith(usernameToMatch, StringComparison.OrdinalIgnoreCase) Order By u).ToArray
    Else
      Throw New ProviderException("Role '{0}' does not exist.".FormatThis(roleName))
    End If
  End Function

  Public Overrides Function GetAllRoles() As String()
    Return (From r In _rolesRepository.Roles Select r.Name).ToArray
  End Function

  Public Overrides Function GetRolesForUser(ByVal username As String) As String()
    Call CheckUserName(username)

    Return (From r In _rolesRepository.Roles Where r.Users.Contains(username) Select r.Name).ToArray
  End Function

  Public Overrides Function GetUsersInRole(ByVal roleName As String) As String()
    Call CheckRoleName(roleName)

    Dim role As Role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

    If Not role Is Nothing Then
      Return role.Users.ToArray
    Else
      Return New String() {}
    End If
  End Function

  Public Overrides Function IsUserInRole(ByVal username As String, ByVal roleName As String) As Boolean
    Call CheckRoleName(roleName)
    Call CheckUserName(username)

    Dim role As Role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

    If Not role Is Nothing Then
      Return role.Users.Contains(username)
    Else
      Throw New ProviderException("Role '{0}' does not exist.".FormatThis(roleName))
    End If
  End Function

  Public Overrides Sub RemoveUsersFromRoles(ByVal usernames() As String, ByVal roleNames() As String)
    Call CheckRoleNames(roleNames)
    Call CheckUserNames(usernames)

    For Each roleName In roleNames
      Dim role As Role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

      If Not role Is Nothing Then
        Call role.Users.RemoveAll(Function(u) usernames.Contains(u))
      Else
        Throw New ProviderException("Role '{0}' does not exist.".FormatThis(roleName))
      End If
    Next

    Call Save()
  End Sub

  Public Overrides Function RoleExists(ByVal roleName As String) As Boolean
    Call CheckRoleName(roleName)

    Dim role As Role = (From r In _rolesRepository.Roles Where r.Name = roleName).SingleOrDefault

    Return Not (role Is Nothing)
  End Function

  Private Sub CheckRoleName(ByVal roleName As String)
    If roleName Is Nothing Then
      Throw New ArgumentNullException("Role name cannot be null/nothing.")
    ElseIf roleName = "" Then
      Throw New ArgumentException("Role name cannot be empty.")
    ElseIf roleName.Contains(",") Then
      Throw New ArgumentException("Role names cannot contain commas: '{0}'.".FormatThis(roleName))
    End If
  End Sub

  Private Sub CheckRoleNames(ByVal roleNames() As String)
    For Each roleName In roleNames
      Call CheckRoleName(roleName)
    Next
  End Sub

  Private Sub CheckUserName(ByVal username As String)
    If username Is Nothing Then
      Throw New ArgumentNullException("User name cannot be null/nothing.")
    ElseIf username = "" Then
      Throw New ArgumentException("User name cannot be empty.")
    End If
  End Sub

  Private Sub CheckUserNames(ByVal usernames() As String)
    For Each username As String In usernames
      Call CheckUserName(username)
    Next
  End Sub

  Private Sub Save()
    Call _rolesRepository.Save()
  End Sub
End Class